//
//  BudgetRepo.swift
//  KoiAccounting
//
//  Created by 纪洪文 on 15.04.25.
//

import Foundation
class BudgetRepo {
    
    private let detailDao = KoiAccountingDB.standard.getDetailDao()
    private let budgetDao = KoiAccountingDB.standard.getBudgetDao()
    private let categoryDao = KoiAccountingDB.standard.getCategoryDao()

    // 获取总预算
    func getBudgetTotal(month: String, uid: String, bookId: String) -> BudgetOverviewData {
        let netResult = BudgetNetApi.standard.getBudgetTotal(bookId: bookId)
        let localTotal = budgetDao.getBudgetTotal(uid: uid, bookId: bookId)
        switch netResult {
        case .success(let success):
            if var netBudget = success {
                if let local = localTotal {
                    if netBudget.sv ?? 0 > local.sv ?? 0 {
                        netBudget.id = local.id
                        budgetDao.update(budget: netBudget)
                    }
                } else {
                    // 没有本地数据，保存网络数据
                    budgetDao.save(budget: netBudget)
                }
            }
        case .failure(let failure):
            logger.error("getBudgerCategoryList net fail \(failure)")
        }
        // 重新获取总预算
        let budgetTotal = budgetDao.getBudgetTotal(uid: uid, bookId: bookId)
        let total = budgetTotal?.amount ?? 0
        let overview = detailDao.getOverviewAccountingData(month: month, uid: uid, bookId: bookId)
        // 当月支出
        let expenseAmount = overview?.expenseAmount ?? 0.0
        var surplusAmount: Double = 0
        if total != 0 {
            surplusAmount = total - expenseAmount
        }
        return BudgetOverviewData(
            budgetAmount: total,
            expenseAmount: expenseAmount,
            surplusAmount: surplusAmount,
            budgetTotal: budgetTotal
        )
    }
    
    // 获取分类预算，分类预算在服务端
    func getBudgetCategory(month: String = Date().toYearMonth(), uid: String, bookId: String) -> [BudgetCombine] {
        let netResult = BudgetNetApi.standard.getBudgetCategory(bookId: bookId)
        var budgetCategoryList = budgetDao.getBudgerCategoryList(uid: uid, bookId: bookId)
        // 更新本地预算数据
        var localMap = [String : AccountingBudget]()
        budgetCategoryList.forEach { item in
            localMap[item.budgetId ?? ""] = item
        }
        var netList = [AccountingBudget]()
        switch netResult {
        case .success(let success):
            netList = success ?? netList
        case .failure(let failure):
            logger.error("getBudgerCategoryList net fail \(failure)")
        }
        
        netList.forEach { netItem in
            let localItem = localMap[netItem.budgetId ?? ""]
            if let localItem = localItem {
                if localItem.sv ?? 0 < netItem.sv ?? 0 {
                    // 本地更新数据
                    var netItem = netItem
                    netItem.id = localItem.id
                    budgetDao.update(budget: netItem)
                }
            } else {
                // 本地没有数据，是新增数据
                budgetDao.save(budget: netItem)
            }
        }
        
        // 更新数据完毕，再取一次最新的预算数据
        budgetCategoryList = budgetDao.getBudgerCategoryList(uid: uid, bookId: bookId)
        var budgetMap = [String : AccountingBudget]()
        budgetCategoryList.forEach { item in
            budgetMap[item.cid ?? ""] = item
        }
        let cidList = budgetCategoryList.map { $0.cid.orEmpty }
        // 找到类别数据
        let categoryList = categoryDao.getCategoryListByCids(uid: uid, bookId: bookId, cids: cidList)
        // 找子类比数据
        let subCategoryList = categoryDao.getSubCategoryListByParentCids(uid: uid, bookId: bookId, cids: cidList)
        // 变成类别字典
        var categoryMap = [String : AccountingCategory]()
        categoryList.forEach { item in
            categoryMap[item.cid.orEmpty] = item
        }
        var subCategoryMap = [String : String]()
        var subCidList = [String]()
        // 子类比变成字典
        subCategoryList.forEach { item in
            if let cid = item.cid, let parentId = item.parentId {
                subCidList.append(cid)
                subCategoryMap[cid] = parentId
            }
        }
        // 查询当前月的类别支出
        let categoryAmountList = detailDao.getCategoryAmountList(month: month, uid: uid, bookId: bookId, cidList: cidList)
        let subCategoryAmountList = detailDao.getCategoryAmountList(month: month, uid: uid, bookId: bookId, cidList: subCidList)
        var categoryAmountMap = [String : Double]()
        categoryAmountList.forEach { item in
            categoryAmountMap[item.cid.orEmpty] = item.amount
        }
        subCategoryAmountList.forEach { item in
            if let parentId = subCategoryMap[item.cid ?? ""], let amount = categoryAmountMap[parentId] {
                categoryAmountMap[parentId] = (item.amount ?? 0) + amount
            }
        }
        var result = [BudgetCombine]()
        budgetCategoryList.forEach { budget in
            let cid = budget.cid.orEmpty
            let amount = categoryAmountMap[cid]
            let category = categoryMap[cid]
            if let category = category {
                result.append(BudgetCombine(
                    budget: budget,
                    category: category,
                    expenseAmount: amount ?? 0))
            }
        }
        return result.sorted(by: { $0.expenseAmount > $1.expenseAmount })
    }
    
    func save(inputAmount: String, uid: String, bookId: String) -> Result<KoiDataCommonResponse?, KoiNetError> {
        let amount = Double(inputAmount)
        let budgetId = ShortUUID().generate()
        let now = Date()
        let createTime = now.toISODateString()
        var budget = AccountingBudget(
            amount: amount,
            budgetId: budgetId,
            bookId: bookId,
            uid: uid,
            createTime: createTime,
            updateTime: createTime,
            isTotal: 1
        )
        let result = BudgetNetApi.standard.budgetCreate(budgetJson: budget.toJson())
        switch result {
        case .success(let success):
            budget.sv =  success?.lasSv ?? 0
            budgetDao.save(budget: budget)
        case .failure(let failure):
            logger.error("budgetCreate fali \(failure)")
        }
        return result
    }
    
    func update(budget: AccountingBudget) -> Result<KoiDataCommonResponse?, KoiNetError> {
        var budget = budget
        let uid = budget.uid ?? ""
        let bookId = budget.bookId ?? ""
        let budgetId = budget.budgetId ?? ""
        let cid = budget.cid ?? ""
        let amount = budget.amount ?? 0
        if amount == 0 {
            // 金额设置为 0 ，相当于删除预算
            return deleteBudget(budget: budget)
        }
        // 自动更新总预算
        // 是否需要更新总预算
        var needUpdateTotal = false
        // 类别预算总和
        var budgetTotal = budgetDao.getBudgetTotal(uid: uid, bookId: bookId)
        let budgetTotalAmount = budgetTotal?.amount ?? 0
        let categoryTotalAmount = budgetDao.getBudgetCategoryTotal(uid: uid, bookId: bookId)
        let budgetCategoryAmount = budgetDao.getBudgetCategoryAmount(uid: uid, bookId: bookId, cid: cid)
        // 新的类别预算总和
        let newCategoryTotalAmount = categoryTotalAmount - budgetCategoryAmount + amount
        if budget.isTotal == 1 {
            // 总预算不能小于分类预算之和
            if amount < categoryTotalAmount {
                logger.debug("总预算不能小于分类预算之和")
                return Result.failure(KoiNetError.httpFailure(KoiError(message: "总预算不能小于分类预算之和")))
            }
        } else {
            if newCategoryTotalAmount > budgetTotalAmount {
                needUpdateTotal = true
                budgetTotal?.amount = newCategoryTotalAmount
            }
        }
        logger.debug("更新预算")
        var budgetTotalJson: String? = nil
        if needUpdateTotal {
            budgetTotalJson = budgetTotal?.toJson()
        }
        
        let result = BudgetNetApi.standard.budgetUpdate(bookId: bookId, budgetId: budgetId, budgetJson: budget.toJson(), budgetTotalJson: budgetTotalJson)
        switch result {
        case .success(let success):
            budget.sv =  success?.lasSv ?? 0
            budgetDao.update(budget: budget)
            if needUpdateTotal {
                budgetTotal?.sv = budget.sv
                if budgetTotal?.id == nil {
                    // 创建
                    budgetDao.save(budget: budgetTotal)
                } else {
                    // 更新
                    budgetDao.update(budget: budgetTotal)
                }
            }
        case .failure(let failure):
            logger.error("budgetCreate fali \(failure)")
        }
        return result
    }
    
    // 保存类别预算
    func save(inputAmount: String, uid: String, bookId: String, category: AccountingCategory) -> Result<KoiDataCommonResponse?, KoiNetError> {
        let amount = Double(inputAmount) ?? 0
        let budgetId = ShortUUID().generate()
        let now = Date()
        let createTime = now.toISODateString()
        var budgetCategory = AccountingBudget(
            amount: amount,
            budgetId: budgetId,
            cid: category.cid,
            bookId: bookId,
            uid: uid,
            createTime: createTime,
            updateTime: createTime,
            isTotal: 0
        )
        // 是否需要更新总预算
        var needUpdateTotal = false
        // 类别预算总和
        let categoryTotalAmount = budgetDao.getBudgetCategoryTotal(uid: uid, bookId: bookId)
        // 新的类别预算总和
        let newCategoryTotalAmount = categoryTotalAmount + amount
        // 拿到总预算
        var budgetTotal = budgetDao.getBudgetTotal(uid: uid, bookId: bookId)
        // 如果没有总预算，创建总预算，总预算金额等于分类预算金额
        if budgetTotal == nil {
            needUpdateTotal = true
            budgetTotal = createBudgetTotal(amount: newCategoryTotalAmount, uid: uid, bookId: bookId)
        }
        // 如果新的预算总和大于总预算，更新总预算金额
        if newCategoryTotalAmount > (budgetTotal?.amount ?? 0) {
            needUpdateTotal = true
            budgetTotal?.amount = newCategoryTotalAmount
        }
        
        var budgetTotalJson: String? = nil
        if needUpdateTotal {
            budgetTotalJson = budgetTotal?.toJson()
        }
        
        let netResult = BudgetNetApi.standard.budgetCreate(budgetJson: budgetCategory.toJson(), budgetTotalJson: budgetTotalJson)
        switch netResult {
        case .success(let success):
            budgetCategory.sv = success?.lasSv ?? 0
            budgetDao.save(budget: budgetCategory)
            if needUpdateTotal {
                budgetTotal?.sv = budgetCategory.sv
                if budgetTotal?.id == nil {
                    // 创建
                    budgetDao.save(budget: budgetTotal)
                } else {
                    // 更新
                    budgetDao.update(budget: budgetTotal)
                }
            }
        case .failure(let failure):
            logger.error("budgetCreate fali \(failure)")
        }
        
        return netResult
    }
    
    func deleteBudget(budget: AccountingBudget?) -> Result<KoiDataCommonResponse?, KoiNetError>{
        guard var budget = budget else { return Result.failure(KoiNetError.koiError(KoiError(message: "没有设置预算"))) }
        let uid = budget.uid ?? ""
        let bookId = budget.bookId ?? ""
        let budgetId = budget.budgetId ?? ""
        if budget.isTotal == 1 && hasBudgetCategory(uid: uid, bookId: bookId) {
            // 如果是总预算，判断是否有类别预算
            // 删除全部类比预算 todo
            // 正常逻辑不会走到这里
            // let budgetCategoryList = getBudgetCategory(uid: budget.uid ?? "", bookId: budget.bookId ?? "")
            return Result.failure(KoiNetError.koiError(KoiError(message: "请先删除类别预算")))
        }
        budget.makeDelete()
        let result = BudgetNetApi.standard.budgetDelete(bookId: bookId, budgetId: budgetId)
        switch result {
        case .success(let success):
            budget.sv = success?.lasSv ?? 0
            budgetDao.update(budget: budget)
        case .failure(let failure):
            logger.error("budgetDelete fail \(failure)")
        }
        return result
    }
    
    private func hasBudgetCategory(uid: String, bookId: String) -> Bool {
        let list = budgetDao.getBudgerCategoryList(uid: uid, bookId: bookId)
        return !list.isEmpty
    }
    
    private func createBudgetTotal(amount: Double, uid: String, bookId: String) -> AccountingBudget{
        let budgetId = ShortUUID().generate()
        let now = Date()
        let createTime = now.toISODateString()
        let budget = AccountingBudget(
            amount: amount,
            budgetId: budgetId,
            bookId: bookId,
            uid: uid,
            createTime: createTime,
            updateTime: createTime,
            isTotal: 1
        )
        budgetDao.save(budget: budget)
        return budget
    }
    
    func deleteAllBudget(uid: String, bookId: String) -> Result<KoiDataCommonResponse?, KoiNetError> {
        let result = BudgetNetApi.standard.budgetDeleteAll(bookId: bookId)
        switch result {
        case .success(let success):
            let sv = success?.lasSv ?? 0
            do {
                try deleteAll(uid: uid, bookId: bookId, sv: sv)
            } catch {
                return Result.failure(KoiNetError.koiError(KoiError(message: "delete All fail")))
            }
        case .failure(let failure):
            logger.error("budgetDelete fail \(failure)")
        }
        return result
    }
    
    private func deleteAll(uid: String, bookId: String, sv: Int64) throws {
        var budgetList = budgetDao.getAll(uid: uid, bookId: bookId)
        for i in budgetList.indices {
            budgetList[i].makeDelete()
            budgetList[i].sv = sv
        }
        
        try budgetDao.update(list:budgetList)
    }
}
